#include <nds.h>
#include <stdio.h>
#include <dswifi9.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>

#include "screen.h"
#include "wifi.h"

extern char *PlayerNames[MAX_PLAYERS];
extern int CRCs[MAX_PLAYERS];
extern GameMode Mode;
extern GAME Game;
extern int ThisDS;
extern int ThisPlayer;
extern struct SEAT EightSeats[MAX_PLAYERS];
extern struct SEAT SixSeats[MAX_PLAYERS];
extern struct SEAT *Seats;

OUT_BUFFER  *OutBuffer;
IN_BUFFER   *InBuffer;
MsgQElement InMsgQ[4096];

int NumPlayers;
int InMsgQSize;

void ListPlayers(void)
{
    int f;

    prints(TOP, 10, 1, "Players");
    HorizLine(TOP, 10, 3, 7, false);

    for(f = 0 ; f < NumPlayers ; f++)
    {
        prints(TOP, 10, f * 2 + 4, "%-12s", PlayerNames[f]);
    }
}

void HandleLateJoin(char *name, int crc)
{
    // We may just be seeing a packet from a player who's already in the game.
    // This is likely to happen when a new game starts as everyone will be broadcasting
    // their name until they see the first game update.
    int f;

    for(f = 0 ; f < Game.TotPlayers ; f++)
    {
        if(!strcmp(name, Game.Players[f].Name) || crc == Game.Players[f].DSCRC)
        {
            return;
        }
    }

    // Insert the new player into next open seat.
    strcpy(Game.Players[Game.TotPlayers].Name, name);
    Game.Players[Game.TotPlayers].DSCRC = crc;
    PreparePlayer(Game.TotPlayers);

    Game.TotPlayers++;
}

void AddPlayer(char *name, int crc)
{
    int f;
    bool inserted = false;

    if(Mode != gm_Networked || NumPlayers == MAX_PLAYERS)
    {
        return;
    }

    if(Game.SeqNumber > 0 && Game.State != gs_Complete)
    {
        if(Game.ActionOnPlayer == ThisPlayer)
        {
            HandleLateJoin(name, crc);
        }
        return;
    }

    // Find position to add name.  May need to move others down list.
    for(f = 0 ; f < NumPlayers ; f++)
    {
        if(strcmp(name, PlayerNames[f]) < 0)
        {
            // Move rest of players down to insert this one.
            int g;

            for(g = NumPlayers - 1; g >= f ; g--)
            {
                strcpy(PlayerNames[g + 1], PlayerNames[g]);
                CRCs[g + 1] = CRCs[g];
            }
            strcpy(PlayerNames[f], name);
            CRCs[f] = crc;
            inserted = true;
            NumPlayers++;
            break;
        }
        else if(!strcmp(name, PlayerNames[f]))
        {
            // Already have this player.
            return;
        }
    }
    if(!inserted)
    {
        strcpy(PlayerNames[NumPlayers], name);
        CRCs[NumPlayers] = crc;
        NumPlayers++;
    }

    ListPlayers();
}

int CalculateCRC(char *buffer, int length)
{
    int crc;
    int i;

    crc = 0;
    while(--length >= 0)
    {
        crc = crc ^ (int)*buffer++ << 8;
        for(i = 0; i < 8; ++i)
        {
            if(crc & 0x8000)
            {
                crc = crc << 1 ^ 0x1021;
            }
            else
            {
                crc = crc << 1;
            }
        }
    }
    return (crc & 0xFFFF);
}

bool CheckCRC(IN_BUFFER *buffer)
{
    return(buffer->CRC == CalculateCRC(buffer->buffer, buffer->length - BUFF_HEADER_LEN));
}

bool CheckPacket(IN_BUFFER *buffer, int i16sRead)
{
    int expectedLen;
    bool result = true;

    // Check length is what we expected to get.
    expectedLen = (InBuffer->length - BUFF_HEADER_LEN + 1) / 2 + BUFF_HEADER_LEN / 2 + PACKET_HEADER_LEN / 2;

    if(i16sRead != expectedLen)
    {
        prints(BOTTOM, 3, 0, "InLen:%d Read:%d Expect:%d", InBuffer->length, i16sRead, expectedLen);
        result = false;
    }

    if(result)
    {
        result = CheckCRC(buffer);
        if(!result)
        {
            prints(BOTTOM, 3, 0, "CRC check failed");
        }
    }

    return result;
}

void PacketHandler(int packetID, int length)
{
    InMsgQ[InMsgQSize].packetID = packetID;
    InMsgQ[InMsgQSize].length = length;
    InMsgQSize++;
}

bool ReadNetworkBuffer()
{
    static int maxQSize = 0;
    int i16sRead;
    bool gotUpdate = false;

    if(InMsgQSize > maxQSize)
    {
        maxQSize = InMsgQSize;
    }
    if(maxQSize > 4000)
    {
        prints(BOTTOM, 24, 0, "Q:%d", maxQSize);
    }

    while(InMsgQSize)
    {
        InMsgQSize--;
        if(InMsgQ[InMsgQSize].length < 3000)
        {
            i16sRead = Wifi_RxRawReadPacket(InMsgQ[InMsgQSize].packetID, InMsgQ[InMsgQSize].length, (unsigned short *)InBuffer);
        }
        else
        {
            //Ignore large packet
            i16sRead = 0;
            InMsgQSize = 0;
            return false;
        }

        if(i16sRead > 0 && Mode == gm_Networked)
        {
            switch(InBuffer->type)
            {
                case bt_PlayerName:
                    if(CheckPacket(InBuffer, i16sRead))
                    {
                        int ds;

                        memcpy(&ds, InBuffer->buffer, sizeof(int));
                        AddPlayer(InBuffer->buffer + sizeof(int), ds);
                    }
                    break;

                case bt_GameUpdate:
                    {
                        GAME *peek = (GAME *)InBuffer->buffer;
                        if(peek->SeqNumber > Game.SeqNumber && peek->SeqNumber == InBuffer->seqNo)
                        {
                            if(CheckPacket(InBuffer, i16sRead))
                            {
                                memcpy(&Game, InBuffer->buffer, sizeof(GAME));

                                // Stop broadcasting the old packet, broadcast the latest state.
                                // *Unless* we're broadcasting our name.  This would happen if a game
                                // just ended and we're trying to start a new one but other DSes aren't
                                // broadcasting their name yet.
                                if(OutBuffer->type != bt_PlayerName)
                                {
                                    // Disable interrupts while we write to the buffer.
                                    REG_IME = 0;

                                    OutBuffer->length = InBuffer->length;
                                    OutBuffer->CRC = InBuffer->CRC;
                                    OutBuffer->seqNo = InBuffer->seqNo;
                                    OutBuffer->type = InBuffer->type;
                                    memcpy(OutBuffer->buffer, InBuffer->buffer, sizeof(GAME));

                                    REG_IME = 1;
                                }

                                // Synchronize clock
                                Game.LevelStartTime = TimeInSeconds() - (Game.CurrentTime - Game.LevelStartTime);

                                gotUpdate = true;
                            }
                        }
                        break;
                    }
                case bt_None:
                    break;
            }
        }
    }

    return gotUpdate;
}

void SendBuffer(BufferType type, int seqNo, void *buff = NULL, int length = -1)
{
    if(length == -1)
    {
        if(buff)
        {
            length = strlen((char *)buff) + 1;
        }
        else
        {
            length = 0;
        }
    }

    // Disable interrupts while we write to the buffer.
    REG_IME = 0;

    if(buff)
    {
        memcpy(OutBuffer->buffer, buff, length);
    }

    // Add 4 for SeqNo, 4 for BufferType, 4 for length, 4 for CRC
    length += BUFF_HEADER_LEN;

    OutBuffer->type = type;
    OutBuffer->length = length;
    OutBuffer->seqNo = seqNo;
    OutBuffer->CRC = CalculateCRC(OutBuffer->buffer, OutBuffer->length - BUFF_HEADER_LEN);

    // Re-enable interrupts.
    REG_IME = 1;

    if(Wifi_RawTxFrame(OutBuffer->length, 0x0014, (unsigned short *)OutBuffer) != 0)
    {
        prints(BOTTOM, 3, 0, "Error sending.  Len: %d", OutBuffer->length);
    }
}

void ResendBuffer(void)
{
    if(OutBuffer && OutBuffer->type != bt_None)
    {
        if(Wifi_RawTxFrame(OutBuffer->length, 0x0014, (unsigned short *)OutBuffer) != 0)
        {
            prints(BOTTOM, 3, 0, "Error resending.  Len: %d", OutBuffer->length);
        }
    }
}

void SetupWifi(void)
{
    static bool done = false;

    if(!done)
    {
        Wifi_InitDefault(false);
        Wifi_SetPromiscuousMode(1);
        Wifi_EnableWifi();
        Wifi_RawSetPacketHandler(PacketHandler);
        Wifi_SetChannel(9);
        done = true;

        timerStart(1, ClockDivider_1024, 45056, ResendBuffer);
    }
}

void FindPlayers(char *playerName)
{
    touchPosition touch;
    int f;
    bool firstPlayer = true;

    NumPlayers = 0;
    for(f = 0 ; f < MAX_PLAYERS ; f++)
    {
        strcpy(PlayerNames[f], "");
    }

    Cls(TOP);
    Cls(BOTTOM);
    HorizLine(BOTTOM, 12, 9, 7, true);
    HorizLine(BOTTOM, 12, 12, 7, false);
    VertLine(BOTTOM, 11, 10, 2, true);
    VertLine(BOTTOM, 19, 10, 2, false);
    prints(BOTTOM, 13 , 10, "Start");

    AddPlayer(playerName, ThisDS);

    // Broadcast our player name once a second (send once here, resend will continue sending it).
    char buffer[20];
    memcpy(buffer, &ThisDS, sizeof(int));
    strcpy(buffer + sizeof(int), playerName);
    SendBuffer(bt_PlayerName, 0, buffer, sizeof(int) + strlen(playerName) + 1);

    Game.State = gs_Complete;
    while(Game.State == gs_Complete)
    {
        // The WIFI Handler will pick up new players as they broadcast.

        // Remove the Start button?
        if(firstPlayer)
        {
            if(strcmp(playerName, PlayerNames[0]))
            {
                Cls(BOTTOM);
                firstPlayer = false;
            }
        }

        KeyPressed(&touch);
        if(firstPlayer && touch.px >= 96 && touch.px <= 152 &&
           touch.py >= 80 && touch.py <= 96 && NumPlayers > 1)
        {
            Game.TotPlayers = NumPlayers;
            Game.State = gs_Setup;
            break;
        }
        WaitForVBlank();
    }
    Cls(TOP);
    Cls(BOTTOM);
}

void SendGameUpdate(void)
{
    if(Mode == gm_Networked)
    {
        Game.SeqNumber++;
        Game.CurrentTime = TimeInSeconds();
        SendBuffer(bt_GameUpdate, Game.SeqNumber, &Game, sizeof(GAME));
    }
}

void WaitForVBlank(void)
{
    if(!InBuffer)
    {
        InBuffer = (IN_BUFFER *)malloc(sizeof(IN_BUFFER));
        OutBuffer = (OUT_BUFFER *)malloc(sizeof(OUT_BUFFER));
    }

    bool updated = false;

    // Disable interrupts while we catch up with the network buffers.
    REG_IME = 0;
    if(InMsgQSize)
    {
        updated = ReadNetworkBuffer();
    }
    REG_IME = 1;

    if(updated && Game.State != gs_Complete)
    {
        if(ThisPlayer == -1)
        {
			if(Game.MaxPlayers == MAX_PLAYERS)
			{
				Seats = (SEAT *)&EightSeats;
			}
			else
			{
				Seats = (SEAT *)&SixSeats;
			}
            TakeSeat();
        }
        UpdateScreens();
    }

    swiWaitForVBlank();
}
